package com.speedchina.framework.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 反射工具类
 *
 * @author hank
 * @create 2019-01-09 下午3:36
 **/

public class ReflectionUtils extends org.springframework.util.ReflectionUtils {

    static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class.getClass());

    /**
     * 类反射属性缓存  key 类名  value—类属性<key: ,value:字段属性>
     */
    private static final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<Class<?>, Map<String, Field>>();

    /**
     * 类反射方法缓存 key-类名 value-类属性<key: , value:方法属性>
     */
    private static final Map<Class<?>, Map<String, Method>> methodCache = new ConcurrentHashMap<Class<?>, Map<String, Method>>();

    /**
     * 设置值，当默认取值为null时
     * @param target
     * @param fieldName
     * @param value
     */
    public static void setIfNull(Object target, String fieldName, Object value){
        try{
            Object o = getFieldValue(target, fieldName);
            if(o == null) {
                setFieldValue(target, fieldName, value);
            }
        }catch (RuntimeException e){
            // 说明属性不存在或获取有问题,此时不处理
        }
    }


    /**
     * 直接通过反射设置对象属性的值
     * @param target
     * @param fieldName
     * @param fieldValue
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void setFieldValue(Object target, String fieldName, Object fieldValue){

        if(target == null || StringUtils.isEmpty(fieldName)){
            return;
        }
        //map直接返回
        if(target instanceof Map){
            ((Map)target).put(fieldName, fieldValue);
            return;
        }

        Class<?> clazz = null;
        if(target instanceof Class){
            clazz = (Class<?>)target;
        } else {
            clazz = target.getClass();
        }

        Map<String, Field> filedMap = fieldCache.get(clazz);
        //没有属性
        if(filedMap == null || !filedMap.containsKey(fieldName)){

            synchronized (clazz) {
                filedMap = fieldCache.get(clazz);
                if(filedMap == null || !filedMap.containsKey(fieldName)){
                    Field field = findField(clazz, fieldName);
                    if(field != null){
                        field.setAccessible(true);
                        if(filedMap == null){
                            //做个长度缓存
                            filedMap = new ConcurrentHashMap<String, Field>();
                            fieldCache.put(clazz, filedMap);
                        }
                        filedMap.put(fieldName, field);
                    }
                }
            }
        }

        if(filedMap == null || !filedMap.containsKey(fieldName)){
            throw new RuntimeException(String.format("类 [%s] 不存在属性 [%s]", clazz.getCanonicalName(), fieldName));
        }

        Field filed = filedMap.get(fieldName);

        try {
            filed.set(target, fieldValue);
        } catch (Exception e) {
            String message = String.format("类 [%s] 反射访问属性 [%s] 异常!", clazz.getCanonicalName(), fieldName);
            logger.error(message);
            // throw new RuntimeException(message, e);
        }

    }

    public static Object getFieldValue(Object target, String fieldName) {
        if (target == null) {
            return null;
        }
        if (StringUtils.isEmpty(fieldName)) {
            return null;
        }
        //Map直接返回
        if (target instanceof Map) {
            return ((Map<?, ?>) target).get(fieldName);
        }
        Class<?> clazz = null;
        if (target instanceof Class) {
            clazz = (Class<?>) target;
        }else {
            clazz = target.getClass();
        }

        Map<String, Field> fieldMap = fieldCache.get(clazz);
        //此处之前不用 ! 判断同对象多属性获取时有问题
        if (fieldMap == null || !fieldMap.containsKey(fieldName)) {
            synchronized (clazz) {
                fieldMap = fieldCache.get(clazz);
                if (fieldMap == null || !fieldMap.containsKey(fieldName)) {
                    Field field = findField(clazz, fieldName);
                    if (field != null) {
                        field.setAccessible(true);
                        if (fieldMap == null) {
                            fieldMap = new ConcurrentHashMap<String, Field>();
                        }
                        fieldMap.put(fieldName, field);
                        fieldCache.put(clazz, fieldMap);
                    }
                }
            }
        }

        if(fieldMap == null || !fieldMap.containsKey(fieldName)){
            throw new RuntimeException(String.format("类 [%s] 不存在属性 [%s]", clazz.getCanonicalName(), fieldName));
        }

        Field filed = fieldMap.get(fieldName);

        try {
            return filed.get(target);
        } catch (Exception e) {
            String message = String.format("类 [%s] 反射访问属性 [%s] 异常!", clazz.getCanonicalName(), fieldName);
            throw new RuntimeException(message, e);
        }

    }

    /**
     * 获取本类和父类的所有属性
     * @date:  2021/5/13
     * @Description
     * @param [clazz] 类对象
     * @return java.lang.reflect.Field[]
     * @throws
     */
    public static List<Field> getAllFields(Class clazz){
        List<Field> fieldList = new ArrayList<Field>();
        while (clazz != null){
            fieldList.addAll(new ArrayList<Field>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        return fieldList;
    }

    /**
     * 反射取得bean中对应属性的值 包括父类属性
     * @param bean
     * @param fieldName
     * @return
     */
    public static Object getProperty(Object bean, String fieldName) {
        Object obj = getProperty(bean.getClass(),bean,fieldName);
        return obj;
    }

    private static Object getProperty(Class _class,Object bean,String fieldName){
        Object obj = null;
        Field[] fields = _class.getDeclaredFields();
        Field.setAccessible(fields, true);
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (fieldName.equals(field.getName())) {
                try {
                    obj = field.get(bean);
                } catch (IllegalArgumentException e) {
                    logger.error(e.getMessage());
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage());
                }
                break;
            }
        }
        if(obj == null && _class.getGenericSuperclass()!=null){
            obj = getProperty(_class.getSuperclass(), bean, fieldName);
        }
        return obj;
    }

}
